"""
inifile.py

Library for reading and writing ini-style config files. The ini file
structure is provided as a class, with nested classes acting as the
sections.

Usage:

    # declare the ini file structure using a class statement
    class MyIni(IniFile):
        one = TypedAttribute(1)
        two = TypedAttribute(2)
        three = TypedAttribute(3, name='triple')
        class NestedSection(IniSection):
            str1 = TypedAttribute('one', name='str_one')
            str2 = TypedAttribute('two', name='str_two')

    # create a configuration instance
    config = MyIni()

    # read the config file
    inifile.load()
      or
    inifile.read()

    # write it back
    inifile.save()
      or
    inifile.write()

-- sample ini file --
[server]
socketPort = 8080
threadPool = 10

[staticContent]
bitmaps = /var/local/bitmaps

[session]
storageType=ram

"""

from metatemplate import GenericTemplate, GenericAttribute,     TypedAttribute, next_attribute_id
from types import StringType
from inspect import isclass
import re

debug_inifile = 0

class IniSection(GenericTemplate):

    re_section = re.compile(r'^\[(.*)\]')
    re_unquote = re.compile(r'^(?P[\"\']?)(?P.*)(?P=openquote)$')

    def log(self, line):
        """simple auxiliary log functions"""
        if debug_inifile:
            # should use the logger interface!
            print line

    def unquote(self, value):
        """unquotes strings reads from the config file"""
        return self.re_unquote.match(value).groupdict()['content']

    def read(self, fileobj):
        """reads the config file info from a file-like object"""

        for line in fileobj:
            #attr_name_list = [f[0] for f in self.__fields__]
            attr_name_list = self.__attr__.keys()
            line = line.strip()
            if not line: continue
            matchresult = self.re_section.match(line)
            if matchresult:
                sectionname = matchresult.group(1)
                self.log('Found section: %s' % sectionname)
                while sectionname in attr_name_list:
                    # found a known section
                    section = getattr(self, sectionname, None)
                    if isinstance(section, IniSection):
                        self.log('Section: %s' % sectionname)
                        sectionname = section.read(fileobj)
                        self.log('Section finished: %s' % sectionname)
                    else:
                        # section name does refer to a section object
                        # ?should it raise a fatal exception/warning?
                        self.log('Section error: %s' % sectionname)
                        sectionname = None
                else:
                    # section is not a child of this node, it's a
                    # sibling; returns the section name
                    self.log('Unknown section: %s' % sectionname)
                    return sectionname
            else:
                # found an attribute
                name, value = line.split('=',1)
                name = name.strip()
                value = value.strip()
                self.log('Normal attribute: %s = %s' % (name, value))

                # retrieves the attribute using the alternative name
                cls_attr_tuple = self.__class__.__attr__.get(name, None)
                if not cls_attr_tuple:
                    # invalid attribute found in the config file!
                    # ? should it cause a exception or warning ?
                    print '1:', self.__class__.__fields__
                    print '2:', self.__class__.__attr__
                    print '3:', self.__class__.__attr__[name]
                    print '4:', cls_attr_tuple
                    print 'Error - Attribute not found: %s' % (name,)
                    raise KeyError

                # retrieves the name used for binding
                cls_attr_name, cls_attr = cls_attr_tuple

                # sets the instance value
                if isinstance(cls_attr, GenericAttribute):
                    setattr(self, cls_attr_name, self.unquote(value))
                else:
                    # invalid attribute found in the config file!
                    # ? should it cause a exception or warning ?
                    raise KeyError

    def write(self, fileobj, _initialpos = None):
        """writes the config file info to a file-like object"""
        if not _initialpos:
            _initialpos = fileobj.tell()

        for fname, fobj in self.__fields__:
            # looks at the class to retrieve the attribute definition
            cls_attr = getattr(self.__class__, fname, None)
            # gets the attribute value stored in the instance
            ins_attr = getattr(self, fname, None)

            # is a subsection?
            if isclass(cls_attr) and issubclass(cls_attr, IniSection):
                # writes an empty line before the section (if needed)
                currentpos = fileobj.tell()
                if currentpos > _initialpos:
                    fileobj.write('\n')

                # writes section name & section contents
                fileobj.write('[%s]\n' % fname)
                ins_attr.write(fileobj, _initialpos = currentpos)

            # is an attribute?
            elif isinstance(cls_attr, GenericAttribute):
                fileobj.write('%s=%s\n' % (cls_attr.name, str(ins_attr)))

class IniFile(IniSection):
    def load(self, fname=None):
        if not fname:
            fname = self.name + '.ini'
        inifile = open(fname, 'r')
        self.read(inifile)
        inifile.close()

    def save(self, fname=None):
        if not fname:
            fname = self.name + '.ini'
        inifile = open(fname, 'w')
        self.write(inifile)
        inifile.close()
